A recent assignment for a university course I'm taking has me thinking about the way we might expect the behavior of a program to occur. Living in a UNIX context (due to using Linux as my daily driver), I generally expect the utility programs I use to go through the following process:

  1. Start up.
  2. Gather input.
  3. Process input.
  4. Output result or show an error, and quit.

This sounds pretty normal in my eyes, but some teachers are inclined more towards the process of:

  1. Start program.
  2. Program asks for input.
  3. If input is correct, process it; if not, loop back and ask user to try again.

This process is expected to happen for every single input expected from the user for the lifetime of the process. This comes off as pretty clunky, and the code reflects that.

use std::io;
use regex::Regex;

fn main() {
    let re_year = Regex::new(r"^[1-9]+$").unwrap();
    let re_confirm = Regex::new(r"^(?:y|n)$").unwrap();

    loop {
        let mut year_input = String::new();
        loop {
            println!("Ingrese un año: ");
            io::stdin().read_line(&mut year_input).expect("Fallo");

            if re_year.is_match(year_input.trim()) {
                break;
            }
            println!("La entrada necesita ser de números solamente");
        }

        let clean_input = year_input.trim();
        if let Ok(year) = clean_input.parse::<u32>() {
            if is_leap_year(year) {
                println!("El año {} es bisiesto", clean_input);
            } else {
                println!("El año {} NO es bisiesto", clean_input);
            }
        } else {
            println!("La entrada no es un número válido");
        }

        let mut must_confirm = true;
        while must_confirm {
            println!("Desea continuar? [y/n]");
            let mut confirm = String::new();
            io::stdin().read_line(&mut confirm).expect("confirmación falló");
            if re_confirm.is_match(confirm.trim()) {
                must_confirm = false;
                if confirm.trim() == "n" {
                    return;
                }
            }
        }
    }
}

fn is_leap_year(year: u32) -> bool {
    year % 4 == 0 && (year % 100 != 0 || year % 400 == 0)
}

As you can see, there are quite a few loops that exist only to nag the user into providing a correct input. This, in my eyes, becomes an incredibly annoying way of using a program.

Input validation in this style might work well when it comes to using graphically presented forms, where the person can re-evaluate the information they introduced; it is not so for interactive inputs in a one-shot CLI program since it's incredibly uncomfortable to "go back" and edit different inputs you've previously provided.

A much simpler (and actually closer in practicality to normal web forms) approach is to use flags or command line arguments, this allows the person to edit, in a single view, all the information they are passing to the program even before said program executes. This also allows for a much more standard way of designing said program, as most languages either provide a library designed for CLI applications, or the ecosystem has a few third-party libraries for the same purpose. In the case of Rust (the language used to make the program above), there's the (clap crate)[https://docs.rs/clap/latest/clap/], which provides many useful functions for this endeavor.